//
//  String+SGExtension.swift
//  QingLiuSwift
//
//  Created by xyanl on 2024/11/1.
//  Copyright © 2024 考拉🐨. All rights reserved.
//

import Foundation
import UIKit
import CryptoKit
import CommonCrypto
import NaturalLanguage

extension String: SpadgerCompatible {}

extension String {
    /// 字符串 转 URL
    var toURL: URL? {
        URL(string: self)
    }
    
    /// 根据图片名创建图片
    var toImage: UIImage {
        UIImage(named: self)!
    }
    
    /// 传入base64的字符串，可以是没有经过修改的转换成的以data开头的，也可以是base64的内容字符串，然后转换成UIImage
    var base64ToImage: UIImage? {
        var str = self
        
        // 1、判断用户传过来的base64的字符串是否是以data开口的，如果是以data开头的，那么就获取字符串中的base代码，然后在转换，如果不是以data开头的，那么就直接转换
        if str.hasPrefix("data:image") {
            guard let newBase64String = str.split(separator: ",").last else {
                return nil
            }
            str = String(newBase64String)
        }
        
        // 2、将处理好的base64String代码转换成NSData
        guard let imgNSData = NSData(base64Encoded: str, options: NSData.Base64DecodingOptions()) else {
            return nil
        }
        
        // 3、将NSData的图片，转换成UIImage
        guard let codeImage = UIImage(data: imgNSData as Data) else {
            return nil
        }
        
        return codeImage
    }
    
    /// 根据十六进制颜色字符串，转颜色， 例子："FFFFFF" -> white
    var toColor: UIColor {
        UIColor(hex: self)
    }
    
    /// String 转 Data
    var toData: Data? {
        data(using: .utf8)
    }
    
    /// JSON字符串 转 字典
    func toDictionary() -> Dictionary<String, Any>? {
        (try? JSONSerialization.jsonObject(with: self.data(using: String.Encoding.utf8,allowLossyConversion: true)!, options: JSONSerialization.ReadingOptions.mutableContainers)) as? Dictionary<String, Any>
    }
    
    /// JSON字符串 转 Array
    func toArray() -> Array<Any>? {
        let jsonString = self
        guard let jsonData: Data = jsonString.data(using: .utf8) else {
            return nil
        }
        let array = try? JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers)
        if array != nil {
            return (array as! Array<Any>)
        }
        return nil
    }
    
    /// 转成拼音
    /// - Parameter isLatin: true：带声调，false：不带声调，默认 false
    /// - Returns: 拼音
    func toPinyin(_ isTone: Bool = false) -> String {
        let mutableString = NSMutableString(string: self)
        CFStringTransform(mutableString, nil, kCFStringTransformToLatin, false)
        if !isTone {
            // 不带声调
            CFStringTransform(mutableString, nil, kCFStringTransformStripDiacritics, false)
        }
        return mutableString as String
    }
    
    /// 提取首字母, "爱国" --> AG
    /// - Parameter isUpper:  true：大写首字母，false: 小写首字母，默认 true
    /// - Returns: 字符串的首字母
    func pinyinInitials(_ isUpper: Bool = true) -> String {
        let pinyin = toPinyin(false).components(separatedBy: " ")
        let initials = pinyin.compactMap { String(format: "%c", $0.cString(using:.utf8)![0]) }
        return isUpper ? initials.joined().uppercased() : initials.joined()
    }
    
    /// 字符串 转 ViewController
    var toViewController: UIViewController? {
        // 获swift中的命名空间名
        if  let appName: String = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String {
            // 拼接命名空间和类名，”包名.类名“
            let classStringName = "\(appName).\(self)"
            // 通过NSClassFromString获取到最终的类
            let classType = NSClassFromString(classStringName) as? UIViewController.Type
            if let type = classType {
                let newVC = type.init()
                return newVC
            }
        }
        return nil
    }
    
}

// MARK: - 字符串 转 基础类型
public extension Spadger where Base == String {
    
    /// String 转 Int
    var toInt: Int {
        Int(Double(base) ?? 0.0)
    }
    
    /// String 转 Double
    var toDouble: Double {
        Double(base) ?? 0.0
    }
    
    /// String 转 CGFloat
    var toCGFloat: CGFloat {
        CGFloat(Double(base) ?? 0.0)
    }
    
    /// String 转 Float
    var toFloat: Float {
        Float(Double(base) ?? 0.0)
    }
    
    /// String 转 Bool
    var toBool: Bool {
        switch (base).lowercased() {
        case "true", "t", "yes", "y", "1":
            return true
        case "false", "f", "no", "n", "0":
            return false
        default:
            return false
        }
    }
    
    /// String 转 NSString
    var toNSString: NSString {
        base as NSString
    }
    
    /// String 转 Int64
    var toInt64Value: Int64 {
        Int64(base) ?? 0
    }
    
    /// String 转 NSNumber
    var toNumber: NSNumber {
        NSNumber(value: self.toDouble)
    }
}

// MARK: - 数字计算
public extension Spadger where Base == String {
    
    /// 字符串小数处理小数取舍
    /// - Parameters:
    ///   - roundingMode: 取舍方式
    /// .plain: 四舍五入
    /// .down: 向下取整
    /// .up: 向上取整
    /// .bankers: 特殊的四舍五入，碰到保留位数后一位的数字为5时，根据前一位数字的奇偶性决定向下还是向上取整， 为偶时向下取整，为奇数时向上取整。如：1.25保留1位小数。5之前的数字是2，2是偶数则向下取整1.2； 1.35保留1位小数时，5之前的数字是3, 3是奇数则向上取整1.4。
    ///   - scale: 小数点后保留的位数
    /// - Returns: nil：无效数字，
    func decimal(_ roundingMode: NSDecimalNumber.RoundingMode = .plain, scale: Int16 = 2) -> String {
        
        let string = (base)
        
        if string.isEmpty {
            return "0"
        }
        
        if !string.sg.isValidNumberAndDecimalPoint() {
            return "0"
        }
        
        let amountHandler = NSDecimalNumberHandler(
            roundingMode: roundingMode,
            scale: scale,
            /// raiseOnExactness: 发生精确错误时是否抛出异常，一般为NO
            raiseOnExactness: false,
            /// raiseOnOverflow: 发生溢出错误时是否抛出异常，一般为NO
            raiseOnOverflow: false,
            /// raiseOnUnderflow: 发生不足错误时是否抛出异常，一般为NO
            raiseOnUnderflow: false,
            /// raiseOnDivideByZero: 被0除时是否抛出异常，一般为YES
            raiseOnDivideByZero: true
        )
        return NSDecimalNumber(string: string).rounding(accordingToBehavior: amountHandler).stringValue
    }
    
    
    /// 加：＋
    /// - Parameter string: string description
    /// - Returns: description
    func adding(_ string: String?) -> String {
        var ln = NSDecimalNumber(string: base)
        var rn = NSDecimalNumber(string: string)
        if ln.doubleValue.isNaN {
            ln = NSDecimalNumber.zero
        }
        if rn.doubleValue.isNaN {
            rn = NSDecimalNumber.zero
        }
        let final = ln.adding(rn)
        return final.stringValue
    }
    
    /// 减：-
    /// - Parameter string: string description
    /// - Returns: description
    func subtracting(_ string: String?) -> String {
        var ln = NSDecimalNumber(string: base)
        var rn = NSDecimalNumber(string: string)
        if ln.doubleValue.isNaN {
            ln = NSDecimalNumber.zero
        }
        if rn.doubleValue.isNaN {
            rn = NSDecimalNumber.zero
        }
        let final = ln.subtracting(rn)
        return final.stringValue
    }
    
    /// 乘：*
    /// - Parameter string: string description
    /// - Returns: description
    func multiplying(_ string: String?) -> String {
        var ln = NSDecimalNumber(string: base)
        var rn = NSDecimalNumber(string: string)
        if ln.doubleValue.isNaN {
            ln = NSDecimalNumber.zero
        }
        if rn.doubleValue.isNaN {
            rn = NSDecimalNumber.zero
        }
        let final = ln.multiplying(by: rn)
        return final.stringValue
    }
    
    
    /// 除：/
    /// - Parameter string: string description
    /// - Returns: description
    func dividing(_ string: String?) -> String {
        var ln = NSDecimalNumber(string: base)
        var rn = NSDecimalNumber(string: string)
        if ln.doubleValue.isNaN {
            ln = NSDecimalNumber.zero
        }
        if rn.doubleValue.isNaN {
            rn = NSDecimalNumber.one
        }
        if rn.doubleValue == 0 {
            rn = NSDecimalNumber.one
        }
        let final = ln.dividing(by: rn)
        return final.stringValue
    }
    
    /// 乘方
    /// - Parameter toPower: 幂数
    func raising(_ toPower: Int) -> String {
        NSDecimalNumber(string: base).raising(toPower: toPower).stringValue
    }
    
}

// MARK: - 扩展方法
public extension Spadger where Base == String {
    
    /// 字符串 Base64 编码
    var base64Encode: String? {
        guard let codingData = base.data(using: .utf8) else {
            return nil
        }
        return codingData.base64EncodedString()
    }
    
    /// 字符串 Base64 解码
    var base64Decode: String? {
        guard let decryptionData = Data(base64Encoded: base, options: .ignoreUnknownCharacters) else {
            return nil
        }
        return String(data: decryptionData, encoding: .utf8)
    }
    
    /// URLs的转义
    /// 这个扩展示例，需要您很容易的把常规字符串"like this" 转义成url编码的"like%20this"字符串:
    var urlEscaped: String {
        base.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
    }
    
    /// 前面用 0 补位，例：25
    /// - Parameter count: 补位后需要的总长度 例：5
    /// - Returns: "00025"
    func beforeAddZore(count: Int) -> String {
        if base.count > count {
            return base
        }
        let index = "10".sg.raising(count)
        let res = index.sg.adding(base)
        return (res as NSString).substring(from: 1)
    }
    
    /// 随机字符串
    /// - Parameters:
    ///   - count: 长度
    ///   - isUppercased: 是否包含大写字母
    /// - Returns: description
    static func randomString(_ count: Int, isUppercased: Bool = false) -> String {
        
        var scource = "0123456789abcdefghijklmnopqrstuvwxyz"
        if isUppercased {
            scource += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        }
        
        var result = ""
        for _ in 0..<count {
            let index = Int(arc4random()%UInt32(scource.count))
            let subString = scource.dropFirst(index).prefix(1)
            result += subString
        }
        
        return result
    }
    
    /// 截取字符串
    /// - Parameter nsRange: nsRange description
    /// - Returns: substring
    func substring(_ nsRange: NSRange) -> String {
        if nsRange.location + nsRange.length > base.count {
            print("截取的长度超出原 String 的长度")
            return ""
        }
        // dropFirst 丢掉前n位,保留剩余的字符串
        // prefix 保留前n位的字符串
        return String(base.dropFirst(nsRange.location).prefix(nsRange.length))
    }
    
    
    /// 字符串通过 label 计算 Size
    /// - Parameters:
    ///   - width: 字符串最大的宽度
    ///   - height: 字符串最大的高度
    ///   - font: 字体大小
    ///   - lineSpacing: 行间距
    ///   - lines: 显示的行数, 默认: 0
    /// - Returns: 返回Size
    func size(width: CGFloat, height: CGFloat = CGFLOAT_MAX, font: UIFont, lineSpacing: CGFloat = 0, lines: Int = 0) -> CGSize {
        if base.isEmpty { return .zero }
        
        let rect = CGRect(x: 0, y: 0, width: width, height: CGFLOAT_MAX)
        let label = UILabel(frame: rect)
        label.font = font
        label.text = base
        label.numberOfLines = lines
        let attrStr = NSMutableAttributedString(string: base)
        let paragraphStyle = NSMutableParagraphStyle()
        if lineSpacing > 0 {
            paragraphStyle.lineSpacing = lineSpacing
        }
        attrStr.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: NSMakeRange(0, base.count))
        label.attributedText = attrStr
        return label.sizeThatFits(rect.size)
    }
    
    /// 比较两个版本号
    ///
    /// - Parameters:
    ///   - version: 版本号
    /// - Returns:
    ///     self 比 version 大 返回 1
    ///     self 比 version 小 返回 -1
    ///     self = version 返回 0
    func compareVesion(to version: String) -> Int {
        
        // 当前版本
        let currentVersion = self.base
        
        if currentVersion == version {
            // 两个版本相同
            return 0
        }
        
        let currentVesionArray = currentVersion.split(separator: ".")
        let serverVersionArray = version.split(separator: ".")
        
        // 比较两个版本的位数，拿到最短的位数
        let minCount = min(currentVesionArray.count, serverVersionArray.count)
        
        for index in 0..<minCount {
            
            let current = String(currentVesionArray[index]).sg.toInt
            let server = String(serverVersionArray[index]).sg.toInt
            
            if current > server {
                return 1
            }
            
            if current < server {
                return -1
            }
        }
        
        // 相同位对应的版本数相同, 比较多出的那位
        if currentVesionArray.count < serverVersionArray.count {
            // 服务的版本位数多, 比较新
            return 1
        }
        
        return -1
    }
    
    /// 比较两个版本号
    ///
    /// - Parameters:
    ///   - fromVersion: 版本号
    ///   - toVersion: 版本号
    /// - Returns: description
    static func compareVesion(from fromVersion: String, to toVersion: String) -> Int {
        fromVersion.sg.compareVesion(to: toVersion)
    }
    
    /// 复制
    func copy() {
        UIPasteboard.general.string = base
    }
}

// MARK: - 沙盒路径的获取
/*
 - 1、Home(应用程序包)目录
 - 整个应用程序各文档所在的目录,包含了所有的资源文件和可执行文件
 - 2、Documents
 - 保存应用运行时生成的需要持久化的数据，iTunes同步设备时会备份该目录
 - 需要保存由"应用程序本身"产生的文件或者数据，例如: 游戏进度，涂鸦软件的绘图
 - 目录中的文件会被自动保存在 iCloud
 - 注意: 不要保存从网络上下载的文件，否则会无法上架!
 - 3、Library
 - 3.1、Library/Cache
 - 保存应用运行时生成的需要持久化的数据，iTunes同步设备时不备份该目录。一般存放体积大、不需要备份的非重要数据
 - 保存临时文件,"后续需要使用"，例如: 缓存的图片，离线数据（地图数据）
 - 系统不会清理 cache 目录中的文件
 - 就要求程序开发时, "必须提供 cache 目录的清理解决方案"
 - 3.2、Library/Preference
 - 保存应用的所有偏好设置，IOS的Settings应用会在该目录中查找应用的设置信息。iTunes
 - 用户偏好，使用 NSUserDefault 直接读写！
 - 如果想要数据及时写入硬盘，还需要调用一个同步方法
 - 4、tmp
 - 保存临时文件，"后续不需要使用"
 - tmp 目录中的文件，系统会自动被清空
 - 重新启动手机, tmp 目录会被清空
 - 系统磁盘空间不足时，系统也会自动清理
 - 保存应用运行时所需要的临时数据，使用完毕后再将相应的文件从该目录删除。应用没有运行，系统也可能会清除该目录下的文件，iTunes不会同步备份该目录
 */
public extension Spadger where Base == String {
    /// 获取Home的完整路径名
    /// - Returns: Home的完整路径名
    static func homeDirectory() -> String {
        // 获取程序的Home目录
        NSHomeDirectory()
    }
    
    /// 获取Documnets的完整路径名
    /// - Returns: Documnets的完整路径名
    static func documnetsDirectory() -> String {
        // 获取程序的documentPaths目录
        // 方法1
        // let documentPaths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
        // let documnetPath = documentPaths[0]
        
        // 方法2
        NSHomeDirectory() + "/Documents"
    }
    
    /**
     这个目录下有两个子目录：Caches 和 Preferences
     Library/Preferences目录，包含应用程序的偏好设置文件。不应该直接创建偏好设置文件，而是应该使用NSUserDefaults类来取得和设置应用程序的偏好。
     Library/Caches目录，主要存放缓存文件，iTunes不会备份此目录，此目录下文件不会再应用退出时删除
     */
    /// 获取Library的完整路径名
    /// - Returns: Library的完整路径名
    static func libraryDirectory() -> String {
        // 获取程序的documentPaths目录
        // Library目录－方法1
        // let libraryPaths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.libraryDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
        // let libraryPath = libraryPaths[0]
        //
        // Library目录－方法2
        NSHomeDirectory() + "/Library"
    }
    
    /// 获取/Library/Caches的完整路径名
    /// - Returns: /Library/Caches的完整路径名
    static func cachesDirectory() -> String {
        // 获取程序的/Library/Caches目录
        NSHomeDirectory() + "/Library/Caches"
    }
    
    /// 获取Library/Preferences的完整路径名
    /// - Returns: Library/Preferences的完整路径名
    static func preferencesDirectory() -> String {
        // Library/Preferences目录－方法2
        NSHomeDirectory() + "/Library/Preferences"
    }
    
    /// 获取Tmp的完整路径名，用于存放临时文件，保存应用程序再次启动过程中不需要的信息，重启后清空。
    /// - Returns: Tmp的完整路径名
    static func tmpDirectory() -> String {
        // 方法1
        // let tmpDir = NSTemporaryDirectory()
        // 方法2
        NSHomeDirectory() + "/tmp"
    }
    
}

// MARK: - CharacterSet（字符集）
/**
 CharacterSet 是在 Foundation 框架下的一个结构体，用于搜索操作的一组 Unicode 字符值。
 官方的API地址：https://developer.apple.com/documentation/foundation/characterset
 概述
 字符集表示一组符合unicode的字符。基础类型使用字符集将字符组合在一起进行搜索操作，以便在搜索期间可以找到任何特定的字符集。
 这种类型提供了“写时复制”的行为，并且还连接到Objective-C NSCharacterSet类。
 总之就是将unicode字符，按组分类，便于搜索查找，验证字符串。通常我们在一些场景下会用到一个字符串是否包含某种特定字符，比如判断密码是否只包含数字，检查url是否有不规范字符，删除多余空格等操作
 
 trimmingCharacters，这个方法除了可以清除前端或后端多余的空白，还可以清除其他指定的字符。
 
 CharacterSet.alphanumerics
 
 controlCharacters: 控制符
 whitespaces: 空格
 whitespacesAndNewlines: 空格和换行
 decimalDigits: 0-9的数字，也不包含小数点
 letters: 所有英文字母，包含大小写 65-90 97-122
 lowercaseLetters:  小写英文字母 97-122
 uppercaseLetters:  大写英文字母 65-90
 nonBaseCharacters: 非基础字符 M*
 alphanumerics: 字母和数字的组合，包含大小写, 不包含小数点
 decomposables: 可分解
 illegalCharacters: 不合规字符，没有在Unicode 3.2 标准中定义的字符
 punctuationCharacters: 标点符号，连接线，引号什么的 P*
 capitalizedLetters: 字母，首字母大写，Lt类别
 symbols: 符号，包含S* 所有内容，运算符，货币符号什么的
 newlines: 返回一个包含换行符的字符集，U+000A ~ U+000D, U+0085, U+2028, and U+2029
 urlUserAllowed:
 urlPasswordAllowed:
 urlHostAllowed:
 urlPathAllowed:
 urlQueryAllowed:
 urlFragmentAllowed:
 bitmapRepresentation:
 inverted:                                    相反的字符集。例如CharacterSet.whitespaces.inverted 就是没有空格
 */
public extension Spadger where Base == String {
    /// 去掉所有空格
    var removeAllSapce: String {
        base.replacingOccurrences(of: " ", with: "", options: .literal, range: nil)
    }
    
    /// 去掉所有换行
    var removeAllLinefeed: String {
        base.replacingOccurrences(of: "\n", with: "", options: .literal, range: nil)
    }
    
    /// 是否是 0-9 的数字，也不包含小数点
    /// - Returns: 结果
    func isNumber() -> Bool {
        /// 0-9的数字，也不包含小数点
        /// trimmingCharacters 清除所有得数字，还有count 表示不是纯数字
        base.trimmingCharacters(in: .decimalDigits).count == 0
    }
    
    /// 删除指定的字符
    /// - Parameter characterString: 指定的字符
    /// - Returns: 返回删除后的字符
    func removeCharacter(_ characterString: String) -> String {
        base.trimmingCharacters(in: CharacterSet(charactersIn: characterString))
    }
    
    /// 隐藏手机号中间的几位
    /// - Parameter combine: 隐藏的字符串(替换的类型)
    /// - Returns: 返回隐藏的手机号
    func hide12BitsPhone(combine: String = "****") -> String {
        if base.count >= 11 {
            let pre = self.sub(start: 0, length: 3)
            let post = self.sub(start: 7, length: 4)
            return pre + combine + post
        } else {
            return base
        }
    }
    
    /// 判断是否全是字母，长度为0返回false
    var isLetters: Bool {
        if base.isEmpty {
            return false
        }
        return base.trimmingCharacters(in: NSCharacterSet.letters) == ""
    }
    
    /// 判断是否是中文, 这里的中文不包括数字及标点符号
    var isChinese: Bool {
        predicateValue(rgex: "(^[\u{4e00}-\u{9fef}]+$)")
    }
    
    /// 判断是否是有效的电子邮件地址
    var isValidEmail: Bool {
        predicateValue(rgex: "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}")
    }
    
    
    /// 是否包含表情，true：包含
    var isContainsEmoji: Bool {
        let controlCharacters = CharacterSet.controlCharacters
        let emojiCharacters = CharacterSet.symbols
        return base.rangeOfCharacter(from: controlCharacters.union(emojiCharacters)) != nil
    }
    
    /// 检测字符串是否包含 Emoji
    func containsEmoji() -> Bool {
        for scalar in base.unicodeScalars {
            switch scalar.value {
            case 0x1F600...0x1F64F, // Emoticons
                0x1F300...0x1F5FF, // Misc Symbols and Pictographs
                0x1F680...0x1F6FF, // Transport and Map
                0x1F700...0x1F77F, // Alchemical Symbols
                0x1F780...0x1F7FF, // Geometric Shapes Extended
                0x1F800...0x1F8FF, // Supplemental Arrows-C
                0x1F900...0x1F9FF, // Supplemental Symbols and Pictographs
                0x1FA00...0x1FA6F, // Chess Symbols
                0x1FA70...0x1FAFF, // Symbols and Pictographs Extended-A
                0x2600...0x26FF,   // Misc Symbols
                0x2700...0x27BF,   // Dingbats
                0xFE00...0xFE0F,   // Variation Selectors
                0x1F1E6...0x1F1FF, // Flags
                0x1F900...0x1F9FF, // Supplemental Symbols and Pictographs
                0x1F018...0x1F270, // Various Asian characters
                0x238C...0x2454,   // Misc items
                0x20D0...0x20FF:   // Combining Diacritical Marks for Symbols
                return true
            default:
                continue
            }
        }
        return false
    }
    
    
    /// 检测当前文字是何种语言
    /// - Returns: 枚举,语言类型 中文 zh-Hans
    var detectLanguage: NLLanguage? {
        let recognizer = NLLanguageRecognizer()
        recognizer.processString(base)
        return recognizer.dominantLanguage
    }
    
    /// 是否为数字或者小数点(字符串的组成是：0-9之间的数字或者小数点即可)
    /// - Returns: 返回结果
    func isValidNumberAndDecimalPoint() -> Bool {
        guard base.count > 0 else {
            return false
        }
        let rgex = "^[\\d.]*$"
        return predicateValue(rgex: rgex)
    }
}

// MARK: - 字符串截取的操作
public extension Spadger where Base == String {
    /// 截取字符串从开始到 index
    /// - Parameter index: 截取到的位置
    /// - Returns: 截取后的字符串
    func sub(to index: Int) -> String {
        let end_Index = validIndex(original: index)
        return String(base[base.startIndex ..< end_Index])
    }
    
    /// 截取字符串从index到结束
    /// - Parameter index: 截取结束的位置
    /// - Returns: 截取后的字符串
    func sub(from index: Int) -> String {
        let start_index = validIndex(original: index)
        return String(base[start_index ..< base.endIndex])
    }
    
    /// 获取指定位置和大小的字符串
    /// - Parameters:
    ///   - start: 开始位置
    ///   - length: 长度
    /// - Returns: 截取后的字符串
    func sub(start: Int, length: Int = -1) -> String {
        var len = length
        if len == -1 || (start + length) > base.count {
            len = base.count - start
        }
        let st = base.index(base.startIndex, offsetBy: start)
        let en = base.index(st, offsetBy: len)
        let range = st ..< en
        return String(base[range]) // .substring(with:range)
    }
    
    /**
     CountableClosedRange：可数的闭区间，如 0...2
     CountableRange：可数的开区间，如 0..<2
     ClosedRange：不可数的闭区间，如 0.1...2.1
     Range：不可数的开居间，如 0.1..<2.1
     */
    /// 切割字符串(区间范围 前闭后开)
    /// - Parameter range: 范围
    /// - Returns: 切割后的字符串
    func slice(_ range: CountableRange<Int>) -> String {
        // 如 slice(2..<6)
        /**
         upperBound（上界）
         lowerBound（下界）
         */
        let startIndex = validIndex(original: range.lowerBound)
        let endIndex = validIndex(original: range.upperBound)
        guard startIndex < endIndex else {
            return ""
        }
        return String(base[startIndex ..< endIndex])
    }
    
    /// 子字符串第一次出现的位置
    /// - Parameter sub: 子字符串
    /// - Returns: 返回字符串的位置（如果内部不存在该字符串则返回 -1）
    func positionFirst(of sub: String) -> Int {
        position(of: sub)
    }
    
    /// 子字符串第一次出现的位置
    /// - Parameter sub: 子字符串
    /// - Returns: 返回字符串的位置（如果内部不存在该字符串则返回 -1）
    func positionLast(of sub: String) -> Int {
        position(of: sub, backwards: true)
    }
    
    
    
    /// 校验字符串位置是否合理，并返回String.Index
    /// - Parameter original: 位置
    /// - Returns: String.Index
    func validIndex(original: Int) -> String.Index {
        switch original {
        case ...base.startIndex.utf16Offset(in: base):
            return base.startIndex
        case base.endIndex.utf16Offset(in: base)...:
            return base.endIndex
        default:
            return base.index(base.startIndex, offsetBy: original > base.count ? base.count : original)
        }
    }
    
    /// 返回(第一次/最后一次)出现的指定子字符串在此字符串中的索引，如果内部不存在该字符串则返回 -1
    /// - Parameters:
    ///   - sub: 子字符串
    ///   - backwards: 如果backwards参数设置为true，则返回最后出现的位置
    /// - Returns: 位置
    private func position(of sub: String, backwards: Bool = false) -> Int {
        var pos = -1
        if let range = base.range(of: sub, options: backwards ? .backwards : .literal) {
            if !range.isEmpty {
                pos = base.distance(from: base.startIndex, to: range.lowerBound)
            }
        }
        return pos
    }
    
    /// 字符串拆分为字符串数组
    /// - Parameter length: 步长
    /// - Returns: description
    func splitStringArray(length: Int) -> [String] {
        let string = base
        var subStrings: [String] = []
        guard string.count > length else {
            return subStrings
        }
        // 当前截取的位置
        var currentLength = 0
        while currentLength < string.count {
            if (currentLength + length) > string.count {
                subStrings.append(string.sg.sub(from: currentLength))
            } else {
                let value = string.sg.sub(start: currentLength, length: length)
                subStrings.append(value)
            }
            currentLength = currentLength + 2
        }
        return subStrings
    }
    
    
    /// 字符串长度不足前面补0
    func prefixAddZero(_ length: Int) -> String {
        insufficientLengthAdZero(length)
    }
    
    /// 字符串长度不足后面补0
    func suffixAddZero(_ length: Int) -> String {
        insufficientLengthAdZero(length, isPrefixAddZer: false)
    }
    
    // MARK: 字符串长度不足补0
    /// 字符串长度不足后面补0
    private func insufficientLengthAdZero(_ length: Int, isPrefixAddZer: Bool = true) -> String {
        let string = base
        guard string.count < length else {
            return string
        }
        let zero = String(repeating: "0", count: length - string.count)
        return isPrefixAddZer ? (zero + string) : (string + zero)
    }
    
    
    /// 查找所有的子字符串所在位置
    /// - Parameter string: 给定的模式P
    func findAllIndex(_ string:String) -> [NSRange]{
        var ranges:[NSRange] = []
        if string.elementsEqual(""){
            return ranges
        }
        let zero = base.startIndex
        let target = Array(string)
        let total = Array(base)
        
        let lenght = string.count
        var startPoint = 0
        
        while total.count >= startPoint + string.count{
            if total[startPoint] == target[0]{
                let startIndex = base.index(zero, offsetBy: startPoint)
                let endIndex = base.index(startIndex, offsetBy: lenght)
                let child = base[startIndex..<endIndex]
                if child.elementsEqual(string){
                    ranges.append(NSRange.init(location: startPoint, length: lenght))
                    startPoint += lenght
                } else {
                    startPoint += 1
                }
            } else {
                startPoint += 1
            }
        }
        
        return ranges
    }
    
    func ranges(of string: String) -> [Range<String.Index>] {
        var rangeArray = [Range<String.Index>]()
        var searchedRange: Range<String.Index>
        guard let sr = base.range(of: base) else {
            return rangeArray
        }
        searchedRange = sr
        
        var resultRange = base.range(of: string, options: .regularExpression, range: searchedRange, locale: nil)
        while let range = resultRange {
            rangeArray.append(range)
            searchedRange = Range(uncheckedBounds: (range.upperBound, searchedRange.upperBound))
            resultRange = base.range(of: string, options: .regularExpression, range: searchedRange, locale: nil)
        }
        return rangeArray
    }
    
    func ranges(of subString: String) -> [NSRange] {
        var nsRanges: [NSRange] = []
        var tempString: NSString = NSString(string: base)
        var count = 0
        
        while tempString.contains(subString) {
            var nsRange = tempString.range(of: subString)
            
            tempString = tempString.replacingCharacters(in: nsRange, with: "") as NSString
            
            nsRange = NSMakeRange(nsRange.location + subString.count * count, nsRange.length)
            
            nsRanges.append(nsRange)
            count += 1
        }
        
        return nsRanges
    }
    
    
    func findAllSubstringRanges(in string: NSString, subString: String) -> [NSRange] {
        var ranges: [NSRange] = []
        
        var tempString: NSString = string
        var count = 0
        
        while tempString.contains(subString) {
            var nsRange = tempString.range(of: subString)
            
            tempString = tempString.replacingCharacters(in: nsRange, with: "") as NSString
            
            nsRange = NSMakeRange(nsRange.location + subString.count * count, nsRange.length)
            
            ranges.append(nsRange)
            count += 1
        }
        
        return ranges
    }
}

// MARK: - 十五、MD5 加密 和 Base64 编解码
/**
 单向散列函数，又被称为消息摘要函数（message digest function），哈希函数
 输出的散列值，也被称为消息摘要（message digest）、指纹（fingerprint）
 
 常见的几种单向散列函数
 MD4、MD5
 产生128bit的散列值，MD就是Message Digest的缩写，目前已经不安全
 Mac终端上默认可以使用md5命令
 SHA-1
 产生160bit的散列值，目前已经不安全
 SHA-2
 SHA-256、SHA-384、SHA-512，散列值长度分别是256bit、384bit、512bit
 SHA-3 全新标准
 */

/// 加密算法
enum CryptoAlgorithm {
    // 加密类型
    case SHA1, SHA224, SHA256, SHA384, SHA512
    
    var HmacAlgorithm: CCHmacAlgorithm {
        var result: Int = 0
        switch self {
        case .SHA1:     result = kCCHmacAlgSHA1
        case .SHA224:   result = kCCHmacAlgSHA224
        case .SHA256:   result = kCCHmacAlgSHA256
        case .SHA384:   result = kCCHmacAlgSHA384
        case .SHA512:   result = kCCHmacAlgSHA512
        }
        return CCHmacAlgorithm(result)
    }
    
    var digestLength: Int {
        var result: Int32 = 0
        switch self {
        case .SHA1:     result = CC_SHA1_DIGEST_LENGTH
        case .SHA224:   result = CC_SHA224_DIGEST_LENGTH
        case .SHA256:   result = CC_SHA256_DIGEST_LENGTH
        case .SHA384:   result = CC_SHA384_DIGEST_LENGTH
        case .SHA512:   result = CC_SHA512_DIGEST_LENGTH
        }
        return Int(result)
    }
}

extension Spadger where Base == String {
    
    /// MD5 加密类型
    enum MD5EncryptType {
        /// 32 位小写
        case lowercase32
        /// 32 位大写
        case uppercase32
        /// 16 位小写
        case lowercase16
        /// 16 位大写
        case uppercase16
    }
    
    // MARK: 15.1、MD5加密 默认是32位小写加密
    /// MD5加密 默认是32位小写加密
    /// - Parameter md5Type: 加密类型
    /// - Returns: MD5加密后的字符串
    func md5Encrypt(_ md5Type: MD5EncryptType = .lowercase32) -> String {
        
        if base.isEmpty {
            print("⚠️⚠️⚠️md5加密无效的字符串⚠️⚠️⚠️")
            return ""
        }
        
        if #available(iOS 13, *) {
            let md5String = Insecure.MD5.hash(data: base.data(using: .utf8)!)
            switch md5Type {
                // 32位小写
            case .lowercase32:
                return md5String.map { String(format: "%02x", $0) }.joined()
                // 32位大写
            case .uppercase32:
                return md5String.map { String(format: "%02X", $0) }.joined()
                // 16位小写
            case .lowercase16:
                let tempStr = md5String.map { String(format: "%02x", $0) }.joined()
                return tempStr.sg.slice(8..<24)
                // 16位大写
            case .uppercase16:
                let tempStr = md5String.map { String(format: "%02X", $0) }.joined()
                return tempStr.sg.slice(8..<24)
            }
        } else {
            // 1.把待加密的字符串转成char类型数据 因为MD5加密是C语言加密
            let cCharArray = base.cString(using: .utf8)
            // 2.创建一个字符串数组接受MD5的值
            var uint8Array = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
            // 3.计算MD5的值
            /*
             第一个参数:要加密的字符串
             第二个参数: 获取要加密字符串的长度
             第三个参数: 接收结果的数组
             */
            CC_MD5(cCharArray, CC_LONG(cCharArray!.count - 1), &uint8Array)
            
            switch md5Type {
                // 32位小写
            case .lowercase32:
                return uint8Array.reduce("") { $0 + String(format: "%02x", $1)}
                // 32位大写
            case .uppercase32:
                return uint8Array.reduce("") { $0 + String(format: "%02X", $1)}
                // 16位小写
            case .lowercase16:
                let tempStr = uint8Array.reduce("") { $0 + String(format: "%02x", $1)}
                return tempStr.sg.slice(8..<24)
                // 16位大写
            case .uppercase16:
                let tempStr = uint8Array.reduce("") { $0 + String(format: "%02X", $1)}
                return tempStr.sg.slice(8..<24)
            }
        }
    }
    
    
    /// 加密，hmac 哈希信息验证码
    /// - Parameters:
    ///   - algorithm: 加密类型
    ///   - key: 密钥
    /// - Returns: 加密的结果，base64
    func hmac(_ algorithm: CryptoAlgorithm, key: String) -> String {
        let str = base.cString(using: .utf8)
        let strLen = Int(base.lengthOfBytes(using: .utf8))
        let digestLen = algorithm.digestLength
        let result = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: digestLen)
        let keyStr = key.cString(using: .utf8)
        let keyLen = Int(key.lengthOfBytes(using: .utf8))
        // 调用 CC_HMAC 函数
        CCHmac(
            algorithm.HmacAlgorithm, // 算法类型
            keyStr!,                 // 密钥指针
            keyLen,                  // 密钥长度
            str!,                    // 消息指针
            strLen,                  // 消息长度
            result                   // 输出缓冲区
        )
        return Data(bytes: result, count: digestLen).base64EncodedString()
    }
}

// MARK: - 正则
private extension Spadger where Base == String {
    func predicateValue(rgex: String) -> Bool {
        let checker: NSPredicate = NSPredicate(format: "SELF MATCHES %@", rgex)
        return checker.evaluate(with: base)
    }
}


extension String {
    /// 从某个位置开始截取：
    /// "1234567890"[from: 5]  结果：67890
    /// - Parameter index: 起始位置
    subscript(from index: Int) -> String? {
        guard index >= 0, index < count else { return nil }
        let startIndex = self.index(startIndex, offsetBy: index)
        let subString = self[startIndex ..< endIndex]
        return String(subString)
    }
    
    /// 从零开始截取到某个位置：
    /// "1234567890"[to: 5]  结果：12345
    /// - Parameter index: 达到某个位置
    subscript(to index: Int) -> String? {
        guard index >= 0, index < count else { return nil }
        let endIndex = self.index(startIndex, offsetBy: index)
        let subString = self[startIndex ..< endIndex]
        return String(subString)
    }
    
    /// 某个范围内截取
    /// "1234567890"[range: 3...8] 结果：456789
    /// - Parameter range: 范围
    subscript<R>(range range: R) -> String? where R: RangeExpression, R.Bound == Int {
        let range = range.relative(to: Int.min ..< Int.max)
        guard range.lowerBound >= 0,
              let lowerIndex = index(startIndex, offsetBy: range.lowerBound, limitedBy: endIndex),
              let upperIndex = index(startIndex, offsetBy: range.upperBound, limitedBy: endIndex)
        else {
            return nil
        }
        return String(self[lowerIndex ..< upperIndex])
    }
}

extension String {
    static func toString(_ value: Any) -> String? {
        
        var result: String? = nil
        
        // 判断Any是否为String类型
        if let stringValue = value as? String {
            // 如果是String类型，进行转换
            result = stringValue
        }
        
        if let intValue = value as? Int {
            result = String(intValue)
        }
        
        if let doubleValue = value as? Double {
            result = String(doubleValue)
        }
        
        return result
    }
}

extension URL {
    /// 获取文件，文件夹的大小 单位:Byte
    func getFileSize() -> UInt64 {
        path.getFileSize()
    }
}

extension String {
    /// 获取文件大小 单位:Byte
    func getFileSize() -> UInt64  {
        var size: UInt64 = 0
        let fileManager = FileManager.default
        // 是否为文件夹
        var isDir: ObjCBool = false
        let isExists = fileManager.fileExists(atPath: self, isDirectory: &isDir)
        // 判断文件存在
        if isExists {
            // 是否为文件夹
            if isDir.boolValue {
                // 迭代器 存放文件夹下的所有文件名
                let enumerator = fileManager.enumerator(atPath: self)
                for subPath in enumerator! {
                    // 获得全路径
                    let fullPath = self.appending("/\(subPath)")
                    do {
                        let attr = try fileManager.attributesOfItem(atPath: fullPath)
                        size += attr[FileAttributeKey.size] as! UInt64
                    } catch  {
                        print("error :\(error)")
                    }
                }
            } else {    // 单文件
                do {
                    let attr = try fileManager.attributesOfItem(atPath: self)
                    size += attr[FileAttributeKey.size] as! UInt64
                    
                } catch  {
                    print("error :\(error)")
                }
            }
        }
        return size
    }
    
    /// 从图片路径中获取图片的尺寸
    /// - Returns: size
    func imageSize() -> CGSize {
        
        if self.count == 0 {
            return .zero
        }
        
        let url = URL(string: self)
        
        guard let lastPath = url?.lastPathComponent else { return .zero }
        
        if !lastPath.contains("_") || !lastPath.contains("x") {
            return .zero
        }
        
        let sizeString = lastPath.components(separatedBy: "_").first! as String
        
        let width: String = String(sizeString.components(separatedBy: "x").first!)
        let height: String = String(sizeString.components(separatedBy: "x").last!)
        
        return CGSize(width: Double(width)!, height: Double(height)!)
    }
}
